package pl.enigmatic.threading;

import java.util.HashSet;
import java.util.Set;


public class Dispatcher extends Thread {

	protected static final long NO_TIMEOUT = 0;
	protected static final long SLEEP_TIME = 1000;
	/** the queue with pending operations */
	protected final Set<Operation<?>> queue = new HashSet<Operation<?>>();
	private boolean alive = true;

	public Dispatcher(String name) {
		setName(name);
		setDaemon(true);
	}

	public Dispatcher() {
		this("");
		setName(getClass().getName() + "-" + Dispatcher.class.getSimpleName());
	}

	protected boolean add(final Operation<?> e) {
		boolean res;
		synchronized (queue) {
			res = queue.add(e);
		}
		interrupt();
		return res;
	}

	/**
	 * Executes the specified delegate asynchronously with the specified arguments on the thread that the BasicDispatcher was created on.
	 * 
	 * @param delegate dispatcher delegate
	 * @return an object, which is returned immediately after beginInvoke is called, that can be used to interact with the delegate as it is pending
	 *         execution in the event queue.
	 */
	public <X> Operation<X> beginInvoke(final Delegate<X> delegate) {
		return beginInvoke(Priority.DEFAULT, delegate);
	}

	/**
	 * Executes the specified delegate asynchronously at the specified priority on the thread the BasicDispatcher is associated with.
	 * 
	 * @param priority dispatcher priority
	 * @param delegate dispatcher delegate
	 * @return an object, which is returned immediately after BeginInvoke is called, that can be used to interact with the delegate as it is pending
	 *         execution in the event queue.
	 */
	public synchronized <X> Operation<X> beginInvoke(final Priority priority, final Delegate<X> delegate) {
		final Operation<X> op = new Operation<X>(priority, delegate);
		add(op);
		return op;
	}

	/**
	 * Executes the specified delegate with the specified arguments synchronously on the thread the BasicDispatcher is associated with.
	 * 
	 * @param delegate dispatcher delegate
	 * @return the return value from the delegate being invoked or null if the delegate has no return value.
	 */
	public <X> X invoke(final Delegate<X> delegate) {
		return invoke(delegate, Priority.DEFAULT, NO_TIMEOUT);
	}

	/**
	 * Executes the specified delegate within the designated time span at the specified priority with the specified arguments synchronously on the
	 * thread the BasicDispatcher is associated with.
	 * 
	 * @param delegate dispatcher delegate
	 * @param timeout the maximum amount of milliseconds to wait for the operation to complete
	 * @return the return value from the delegate being invoked or null if the delegate has no return value.
	 */
	public <X> X invoke(final Delegate<X> delegate, final long timeout) {
		return invoke(delegate, Priority.DEFAULT, timeout);
	}

	/**
	 * Executes the specified delegate at the specified priority with the specified arguments synchronously on the thread the BasicDispatcher is
	 * associated with.
	 * 
	 * @param delegate dispatcher delegate
	 * @param priority dispatcher priority
	 * @return the return value from the delegate being invoked or null if the delegate has no return value.
	 */
	public <X> X invoke(final Delegate<X> delegate, final Priority priority) {
		return invoke(delegate, Priority.DEFAULT, NO_TIMEOUT);
	}

	/**
	 * Executes the specified delegate within the designated time span at the specified priority with the specified arguments synchronously on the
	 * thread the BasicDispatcher is associated with.
	 * 
	 * @param delegate dispatcher delegate
	 * @param timeout the maximum amount of milliseconds to wait for the operation to complete
	 * @param priority dispatcher priority
	 * @return the return value from the delegate being invoked or null if the delegate has no return value.
	 */
	public synchronized <X> X invoke(final Delegate<X> delegate, final Priority priority, final long timeout) {
		final Operation<X> op = new Operation<X>(priority, delegate);
		long wait = timeout;
		add(op);
		while (!op.isCompleted() && (timeout == NO_TIMEOUT || wait > 0)) {
			try {
				final long start = System.currentTimeMillis();
				synchronized (op) {
					op.wait(wait);
				}
				wait = wait - (System.currentTimeMillis() - start);
				wait = wait > 0 ? wait : 0;
			} catch (final Exception e) {
				break;
			}
		}
		return op.result;
	}

	protected boolean hasWorkToDo() {
		return !queue.isEmpty();
	}

	protected boolean hasToStayAlive() {
		return alive;
	}

	public void finish() {
		alive = false;
		interrupt();
	}

	@Override
	public void run() {
		boolean run = true;
		final HashSet<Operation<?>> queue = new HashSet<Operation<?>>();
		while (run) {
			try {
				while (run) {
					sleep(SLEEP_TIME);
					synchronized (this.queue) {
						run = hasToStayAlive() && !hasWorkToDo();
					}
				}
			} catch (final InterruptedException e) {}
			synchronized (this.queue) {
				queue.addAll(this.queue);
				this.queue.removeAll(queue);
			}
			for (final Operation<?> op : queue) {
				op.execute();
				op.finish();
			}
			queue.removeAll(queue);
			synchronized (this.queue) {
				run = hasToStayAlive() || hasWorkToDo();
			}
		}
	}

	public static interface Delegate<X> {

		public X execute();

		// TODO dodać jakąś obsługę powiadamiania o zakończeniu wykonywania, tzn np. metodę:
		// public void finish();

		public static abstract class VoidImpl implements Delegate<Void> {

			public abstract void exec();

			@Override
			public Void execute() {
				exec();
				return null;
			}
		}
	}

	/**
	 * Represents an object that is used to interact with an operation that has been posted to the BasicDispatcher queue.
	 * 
	 * @author reggie_7
	 */
	public final class Operation<X> {

		/** or sets the priority of the operation in the BasicDispatcher queue */
		private final Delegate<X> delegate;
		/** or sets the priority of the operation in the BasicDispatcher queue */
		private final Priority priority;
		/** the current status of the operation. */
		private OperationStatus status = OperationStatus.PENDING;
		/** the result of the operation after it has completed */
		X result = null;

		public Operation(final Priority priority, final Delegate<X> delegate) {
			this.delegate = delegate;
			this.priority = priority;
		}

		public boolean isCompleted() {
			synchronized (this) {
				return status == OperationStatus.COMPLETED || status == OperationStatus.ABORTED;
			}
		}

		void finish() {
			synchronized (this) {
				status = OperationStatus.COMPLETED;
				notifyAll();
			}
		}

		/** @return the BasicDispatcher that the operation was posted to */
		public Dispatcher getDispatcher() {
			return Dispatcher.this;
		}

		/** @return or sets the priority of the operation in the BasicDispatcher queue */
		public Priority getPriority() {
			return priority;
		}

		/** @return the result of the operation after it has completed */
		public Object getResult() {
			return result;
		}

		/** @return the current status of the operation. */
		public OperationStatus getStatus() {
			synchronized (this) {
				return status;
			}
		}

		public void execute() {
			synchronized (this) {
				status = OperationStatus.EXECUTING;
			}
			result = delegate.execute();
		}

	}

	public static enum Priority {
		/** This is an invalid priority */
		INVALID,
		/** Operations are not processed */
		INACTIVE,
		/** Operations are processed when the system is idle */
		SYSTEM_IDLE,
		/** Operations are processed when the application is idle */
		APPLICATION_IDLE,
		/** Operations are processed after background operations have completed */
		CONTEXT_IDLE,
		/**
		 * Operations are processed after all other non-idle operations are completed
		 */
		BACKGROUND,
		/** Operations are processed at the same priority as input */
		INPUT,
		/**
		 * Operations are processed when layout and render has finished but just before items at input priority are serviced. Specifically this is
		 * used when raising the Loaded event
		 */
		LOADED,
		/** Operations processed at the same priority as rendering */
		RENDER,
		/** Operations are processed at the same priority as data binding */
		DATA_BIND,
		/**
		 * Operations are processed at normal priority. This is the typical application priority
		 */
		NORMAL,
		/**
		 * Operations are processed before other asynchronous operations. This is the highest priority
		 */
		SEND;

		static final Priority DEFAULT = NORMAL;
	}

	public enum OperationStatus {
		/** The operation is pending and is still in the BasicDispatcher queue */
		PENDING,
		/** The operation has aborted */
		ABORTED,
		/** The operation is completed */
		COMPLETED,
		/** The operation started executing, but has not completed */
		EXECUTING
	}

}